 /*******************************************************************************
  * Copyright (c) 2000, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.externaltools.internal.model;

 import java.util.HashMap ;
 import java.util.Map ;
 import java.util.StringTokenizer ;

 import org.eclipse.core.resources.ICommand;
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IFolder;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.IncrementalProjectBuilder;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IConfigurationElement;
 import org.eclipse.core.runtime.IExtensionPoint;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.NullProgressMonitor;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.debug.core.DebugPlugin;
 import org.eclipse.debug.core.ILaunchConfiguration;
 import org.eclipse.debug.core.ILaunchConfigurationType;
 import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
 import org.eclipse.debug.core.ILaunchManager;
 import org.eclipse.jface.dialogs.MessageDialog;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.ui.externaltools.internal.registry.ExternalToolMigration;

 /**
  * Utility methods for working with external tool project builders.
  */
 public class BuilderUtils {

     public static final String LAUNCH_CONFIG_HANDLE = "LaunchConfigHandle"; //$NON-NLS-1$

     /**
      * Constant used to find a builder using the 3.0-interim format
      */
     public static final String BUILDER_FOLDER_NAME= ".externalToolBuilders"; //$NON-NLS-1$
 /**
      * Constant used to represent the current project in the 3.0-final format.
      */
     public static final String PROJECT_TAG= "<project>"; //$NON-NLS-1$

     public static final String VERSION_1_0= "1.0"; //$NON-NLS-1$
 public static final String VERSION_2_1= "2.1"; //$NON-NLS-1$
 // The format shipped up to and including Eclipse 3.0 RC1
 public static final String VERSION_3_0_interim= "3.0.interim"; //$NON-NLS-1$
 // The format shipped in Eclipse 3.0 final
 public static final String VERSION_3_0_final= "3.0"; //$NON-NLS-1$

     // Extension point constants.
 private static final String TAG_CONFIGURATION_MAP= "configurationMap"; //$NON-NLS-1$
 private static final String TAG_SOURCE_TYPE= "sourceType"; //$NON-NLS-1$
 private static final String TAG_BUILDER_TYPE= "builderType"; //$NON-NLS-1$

     private static final String BUILD_TYPE_SEPARATOR = ","; //$NON-NLS-1$
 private static final int[] DEFAULT_BUILD_TYPES= new int[] {
                                     IncrementalProjectBuilder.INCREMENTAL_BUILD,
                                     IncrementalProjectBuilder.FULL_BUILD};

     /**
      * Returns a launch configuration from the given ICommand arguments. If the
      * given arguments are from an old-style external tool, an unsaved working
      * copy will be created from the arguments and returned.
      *
      * @param commandArgs the builder ICommand arguments
      * @return a launch configuration, a launch configuration working copy, or
      * <code>null</code> if not possible.
      */
     public static ILaunchConfiguration configFromBuildCommandArgs(IProject project, Map commandArgs, String [] version) {
         String configHandle = (String ) commandArgs.get(LAUNCH_CONFIG_HANDLE);
         if (configHandle == null) {
             // Probably an old-style (Eclipse 1.0 or 2.0) external tool. Try to migrate.
 version[0]= VERSION_1_0;
             return ExternalToolMigration.configFromArgumentMap(commandArgs);
         }
         ILaunchManager manager= DebugPlugin.getDefault().getLaunchManager();
         ILaunchConfiguration configuration= null;
         if (configHandle.startsWith(PROJECT_TAG)) {
             version[0]= VERSION_3_0_final;
             IPath path= new Path(configHandle);
             IFile file= project.getFile(path.removeFirstSegments(1));
             if (file.exists()) {
                 configuration= manager.getLaunchConfiguration(file);
             }
         } else {
             // Try treating the handle as a file name.
 // This is the format used in 3.0 RC1.
 IPath path= new Path(BUILDER_FOLDER_NAME).append(configHandle);
             IFile file= project.getFile(path);
             if (file.exists()) {
                 version[0]= VERSION_3_0_interim;
                 configuration= manager.getLaunchConfiguration(file);
             } else {
                 try {
                     // Treat the configHandle as a memento. This is the format
 // used in Eclipse 2.1.
 configuration = manager.getLaunchConfiguration(configHandle);
                 } catch (CoreException e) {
                 }
                 if (configuration != null) {
                     version[0]= VERSION_2_1;
                 }
             }
         }
         return configuration;
     }

     /**
      * Returns an <code>ICommand</code> from the given launch configuration.
      *
      * @param project the project the ICommand is relevant to
      * @param config the launch configuration to create the command from
      * @return the new command. <code>null</code> can be returned if problems occur during
      * the translation.
      */
     public static ICommand commandFromLaunchConfig(IProject project, ILaunchConfiguration config) {
         ICommand newCommand = null;
         try {
             newCommand = project.getDescription().newCommand();
             newCommand = toBuildCommand(project, config, newCommand);
             configureTriggers(config, newCommand);
         } catch (CoreException exception) {
             Shell shell= ExternalToolsPlugin.getActiveWorkbenchShell();
             if (shell != null) {
                 MessageDialog.openError(shell, ExternalToolsModelMessages.BuilderUtils_5, ExternalToolsModelMessages.BuilderUtils_6);
             }
             return null;
         }
         return newCommand;
     }
     
     public static void configureTriggers(ILaunchConfiguration config, ICommand newCommand) throws CoreException {
         newCommand.setBuilding(IncrementalProjectBuilder.FULL_BUILD, false);
         newCommand.setBuilding(IncrementalProjectBuilder.INCREMENTAL_BUILD, false);
         newCommand.setBuilding(IncrementalProjectBuilder.AUTO_BUILD, false);
         newCommand.setBuilding(IncrementalProjectBuilder.CLEAN_BUILD, false);
         String buildKinds= config.getAttribute(IExternalToolConstants.ATTR_RUN_BUILD_KINDS, (String )null);
         int[] triggers= BuilderUtils.buildTypesToArray(buildKinds);
         for (int i = 0; i < triggers.length; i++) {
             switch (triggers[i]) {
                 case IncrementalProjectBuilder.FULL_BUILD:
                     newCommand.setBuilding(IncrementalProjectBuilder.FULL_BUILD, true);
                     break;
                 case IncrementalProjectBuilder.INCREMENTAL_BUILD:
                     newCommand.setBuilding(IncrementalProjectBuilder.INCREMENTAL_BUILD, true);
                     break;
                 case IncrementalProjectBuilder.AUTO_BUILD:
                     newCommand.setBuilding(IncrementalProjectBuilder.AUTO_BUILD, true);
                     break;
                 case IncrementalProjectBuilder.CLEAN_BUILD:
                     newCommand.setBuilding(IncrementalProjectBuilder.CLEAN_BUILD, true);
                     break;
             }
         }
         if (!config.getAttribute(IExternalToolConstants.ATTR_TRIGGERS_CONFIGURED, false)) {
             ILaunchConfigurationWorkingCopy copy= config.getWorkingCopy();
             copy.setAttribute(IExternalToolConstants.ATTR_TRIGGERS_CONFIGURED, true);
             copy.doSave();
         }
     }

     /**
      * Returns whether the given configuration is an "unmigrated" builder.
      * Unmigrated builders are external tools that are stored in an old format
      * but have not been migrated by the user. Old format builders are always
      * translated into launch config working copies in memory, but they're not
      * considered "migrated" until the config has been saved and the project spec
      * updated.
      * @param config the config to examine
      * @return whether the given config represents an unmigrated builder
      */
     public static boolean isUnmigratedConfig(ILaunchConfiguration config) {
         return config.isWorkingCopy() && ((ILaunchConfigurationWorkingCopy) config).getOriginal() == null;
     }

     /**
      * Converts the given config to a build command which is stored in the
      * given command.
      *
      * @return the configured build command
      */
     public static ICommand toBuildCommand(IProject project, ILaunchConfiguration config, ICommand command) throws CoreException {
         Map args= null;
         if (isUnmigratedConfig(config)) {
             // This config represents an old external tool builder that hasn't
 // been edited. Try to find the old ICommand and reuse the arguments.
 // The goal here is to not change the storage format of old, unedited builders.
 ICommand[] commands= project.getDescription().getBuildSpec();
             for (int i = 0; i < commands.length; i++) {
                 ICommand projectCommand = commands[i];
                 String name= ExternalToolMigration.getNameFromCommandArgs(projectCommand.getArguments());
                 if (name != null && name.equals(config.getName())) {
                     args= projectCommand.getArguments();
                     break;
                 }
             }
         } else {
             if (config instanceof ILaunchConfigurationWorkingCopy) {
                 ILaunchConfigurationWorkingCopy workingCopy= (ILaunchConfigurationWorkingCopy) config;
                 if (workingCopy.getOriginal() != null) {
                     config= workingCopy.getOriginal();
                 }
             }
             args= new HashMap ();
             // Launch configuration builders are stored with a project-relative path
 StringBuffer buffer= new StringBuffer (PROJECT_TAG);
             // Append the project-relative path (workspace path minus first segment)
 buffer.append('/').append(config.getFile().getFullPath().removeFirstSegments(1));
             args.put(LAUNCH_CONFIG_HANDLE, buffer.toString());
         }
         command.setBuilderName(ExternalToolBuilder.ID);
         command.setArguments(args);
         return command;
     }
     
     /**
      * Returns the type of launch configuration that should be created when
      * duplicating the given configuration as a project builder. Queries to see
      * if an extension has been specified to explicitly declare the mapping.
      */
     public static ILaunchConfigurationType getConfigurationDuplicationType(ILaunchConfiguration config) throws CoreException {
         IExtensionPoint ep= Platform.getExtensionRegistry().getExtensionPoint(IExternalToolConstants.PLUGIN_ID, IExternalToolConstants.EXTENSION_POINT_CONFIGURATION_DUPLICATION_MAPS);
         IConfigurationElement[] elements = ep.getConfigurationElements();
         String sourceType= config.getType().getIdentifier();
         String builderType= null;
         for (int i= 0; i < elements.length; i++) {
             IConfigurationElement element= elements[i];
             if (element.getName().equals(TAG_CONFIGURATION_MAP) && sourceType.equals(element.getAttribute(TAG_SOURCE_TYPE))) {
                 builderType= element.getAttribute(TAG_BUILDER_TYPE);
                 break;
             }
         }
         if (builderType != null) {
             ILaunchConfigurationType type= DebugPlugin.getDefault().getLaunchManager().getLaunchConfigurationType(builderType);
             if (type != null) {
                 return type;
             }
         }
         return config.getType();
     }

     /**
      * Returns the folder where project builders should be stored or
      * <code>null</code> if the folder could not be created
      */
     public static IFolder getBuilderFolder(IProject project, boolean create) {
         IFolder folder = project.getFolder(BUILDER_FOLDER_NAME);
         if (!folder.exists() && create) {
             try {
                 folder.create(true, true, new NullProgressMonitor());
             } catch (CoreException e) {
                 return null;
             }
         }
         return folder;
     }

     /**
      * Returns a duplicate of the given configuration. The new configuration
      * will be of the same type as the given configuration or of the duplication
      * type registered for the given configuration via the extension point
      * IExternalToolConstants.EXTENSION_POINT_CONFIGURATION_DUPLICATION_MAPS.
      */
     public static ILaunchConfiguration duplicateConfiguration(IProject project, ILaunchConfiguration config) throws CoreException {
         Map attributes= config.getAttributes();
         String newName= new StringBuffer (config.getName()).append(ExternalToolsModelMessages.BuilderUtils_7).toString();
         newName= DebugPlugin.getDefault().getLaunchManager().generateUniqueLaunchConfigurationNameFrom(newName);
         ILaunchConfigurationType newType= getConfigurationDuplicationType(config);
         ILaunchConfigurationWorkingCopy newWorkingCopy= newType.newInstance(getBuilderFolder(project, true), newName);
         newWorkingCopy.setAttributes(attributes);
         return newWorkingCopy.doSave();
     }

     /**
      * Migrates the launch configuration working copy, which is based on an old-
      * style external tool builder, to a new, saved launch configuration. The
      * returned launch configuration will contain the same attributes as the
      * given working copy with the exception of the configuration name, which
      * may be changed during the migration. The name of the configuration will
      * only be changed if the current name is not a valid name for a saved
      * config.
      *
      * @param workingCopy the launch configuration containing attributes from an
      * old-style project builder.
      * @return ILaunchConfiguration a new, saved launch configuration whose
      * attributes match those of the given working copy as well as possible
      * @throws CoreException if an exception occurs while attempting to save the
      * new launch configuration
      */
     public static ILaunchConfiguration migrateBuilderConfiguration(IProject project, ILaunchConfigurationWorkingCopy workingCopy) throws CoreException {
         workingCopy.setContainer(getBuilderFolder(project, true));
         // Before saving, make sure the name is valid
 String name= workingCopy.getName();
         name= name.replace('/', '.');
         if (name.charAt(0) == ('.')) {
             name = name.substring(1);
         }
         IStatus status = ResourcesPlugin.getWorkspace().validateName(name, IResource.FILE);
         if (!status.isOK()) {
             name = "ExternalTool"; //$NON-NLS-1$
 }
         name = DebugPlugin.getDefault().getLaunchManager().generateUniqueLaunchConfigurationNameFrom(name);
         workingCopy.rename(name);
         return workingCopy.doSave();
     }

     /**
      * Converts the build types string into an array of
      * build kinds.
      *
      * @param buildTypes the string of built types to convert
      * @return the array of build kinds.
      */
     public static int[] buildTypesToArray(String buildTypes) {
         if (buildTypes == null || buildTypes.length() == 0) {
             return DEFAULT_BUILD_TYPES;
         }
         
         int count = 0;
         boolean incremental = false;
         boolean full = false;
         boolean auto = false;
         boolean clean= false;
     
         StringTokenizer tokenizer = new StringTokenizer (buildTypes, BUILD_TYPE_SEPARATOR);
         while (tokenizer.hasMoreTokens()) {
             String token = tokenizer.nextToken();
             if (IExternalToolConstants.BUILD_TYPE_INCREMENTAL.equals(token)) {
                 if (!incremental) {
                     incremental = true;
                     count++;
                 }
             } else if (IExternalToolConstants.BUILD_TYPE_FULL.equals(token)) {
                 if (!full) {
                     full = true;
                     count++;
                 }
             } else if (IExternalToolConstants.BUILD_TYPE_AUTO.equals(token)) {
                 if (!auto) {
                     auto = true;
                     count++;
                 }
             } else if (IExternalToolConstants.BUILD_TYPE_CLEAN.equals(token)) {
                 if (!clean) {
                     clean = true;
                     count++;
                 }
             }
         }
     
         int[] results = new int[count];
         count = 0;
         if (incremental) {
             results[count] = IncrementalProjectBuilder.INCREMENTAL_BUILD;
             count++;
         }
         if (full) {
             results[count] = IncrementalProjectBuilder.FULL_BUILD;
             count++;
         }
         if (auto) {
             results[count] = IncrementalProjectBuilder.AUTO_BUILD;
             count++;
         }
         if (clean) {
             results[count] = IncrementalProjectBuilder.CLEAN_BUILD;
             count++;
         }
     
         return results;
     }
 }

